package cook.services;

import static com.google.common.collect.Lists.newArrayList;
import static cook.db.Tables.INGREDIENTS;
import static cook.db.Tables.REC_ING;

import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrInputDocument;
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;

import cook.models.Ingredient;
import cook.utils.Converter;

@Service
public class SolrService {
    private static final Logger logger = Logger.getLogger(SolrService.class);

    public static final String INGREDIENT_PREFIX = "ingr_";
    private static final String QUERY_FUNCTION = "sqedist";
    private static final double SCALE_UP = 10.;
    private static final double SCALE_DOWN = 0.3;
    private static final double SWEET_SPOT = 0.8;

    @Autowired
    private SolrServer solr;

    @Autowired
    private DSLContext sql;

    public void reindex() throws SolrServerException, IOException {
        addDocuments(sql.select().from(REC_ING).join(INGREDIENTS).on(REC_ING.ID_ING.eq(INGREDIENTS.ADM_ID))
                .fetchInto(Ingredient.class));
    }

    public void addDocuments(final Collection<Ingredient> ingredients) throws SolrServerException, IOException {
        final List<SolrInputDocument> docs = newArrayList();
        final Multimap<Integer, Ingredient> a = decompose(ingredients);
        for (final Integer recipeId : a.keySet()) {
            final SolrInputDocument doc = new SolrInputDocument();
            doc.addField("id", recipeId);
            for (final Ingredient i : a.get(recipeId)) {
                final String fieldName = INGREDIENT_PREFIX + i.getName().replaceAll(" ", "");
                if (doc.getField(fieldName) != null) {
                    doc.setField(fieldName, (Double) doc.getFieldValue(fieldName) + i.getQuantity());
                } else {
                    doc.addField(fieldName, i.getQuantity());
                }
            }
            logger.info("Adding document " + doc);
            docs.add(doc);
        }
        solr.add(docs, 1000);
    }

    public List<Integer> findMLT(final Collection<Ingredient> ingredients, final int limit, final int offset)
            throws SolrServerException {
        final Map<String, Double> ingr = processIngredients(ingredients);
        final SolrQuery solrQuery = new SolrQuery(buildQ(ingr));
        solrQuery.set("sort", buildS(ingr));

        solrQuery.setRows(limit);
        solrQuery.setStart(offset);
        solrQuery.setFields("id");
        logger.info("Query for solr " + solrQuery.getQuery());
        logger.info("Sorting for solr " + solrQuery.get("sort"));

        return Lists.transform(solr.query(solrQuery).getResults(), new Function<SolrDocument, Integer>() {

            @Override
            public Integer apply(final SolrDocument input) {
                return Integer.valueOf(input.getFieldValue("id").toString());
            }
        });
    }

    private Map<String, Double> processIngredients(final Collection<Ingredient> ingredients) {
        final Map<String, Double> m = Maps.newHashMap();
        for (final Ingredient i : ingredients) {
            final String name = processName(i.getName()).toLowerCase();
            m.put(name, Converter.convert(name, i.getUnit(), i.getQuantity()));
        }
        return m;
    }

    private static String processName(final String name) {
        return INGREDIENT_PREFIX + name.replaceAll(" ", "").replaceAll("\\W", "");
    }

    private static String buildQ(final Map<String, Double> ingr) {
        final StringBuilder sb = new StringBuilder();
        sb.append("(").append("id:*").append("^0").append(" || ");
        for (final String i : ingr.keySet()) {
            final Double v = ingr.get(i);
            sb.append(i).append(": ([ ");
            sb.append(SWEET_SPOT * v).append(" TO ").append(v).append(" ]^2 ").append("OR [ ");
            sb.append(SCALE_DOWN * v).append(" TO ").append(SCALE_UP * v);
            sb.append(" ]) || ");
        }
        sb.append(")");
        sb.delete(sb.length() - 4, sb.length() - 1);
        return sb.toString();
    }

    private static String buildS(final Map<String, Double> ingr) {
        final StringBuilder ib = new StringBuilder();
        final StringBuilder vb = new StringBuilder();
        for (final String i : ingr.keySet()) {
            ib.append(i).append(", ");
            vb.append(ingr.get(i)).append(", ");
        }
        vb.delete(vb.length() - 2, vb.length());
        return String.format("score desc, %s(%s %s) asc", QUERY_FUNCTION, ib, vb);
    }

    private Multimap<Integer, Ingredient> decompose(final Collection<Ingredient> ingredients) {
        return Multimaps.index(ingredients, new Function<Ingredient, Integer>() {

            @Override
            public Integer apply(final Ingredient input) {
                return input.getId();
            }
        });
    }

    public void deleteAll() throws SolrServerException, IOException {
        solr.deleteByQuery("id:*");
        solr.commit();
    }
}
